
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Coze API 流式响应测试</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
      line-height: 1.6;
    }
    h1 {
      text-align: center;
      margin-bottom: 30px;
    }
    .container {
      display: flex;
      flex-direction: column;
      gap: 20px;
    }
    .card {
      border: 1px solid #ddd;
      border-radius: 8px;
      padding: 15px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
    }
    .response {
      background-color: #f9f9f9;
      border-radius: 8px;
      padding: 15px;
      min-height: 300px;
      white-space: pre-wrap;
      overflow-wrap: break-word;
    }
    .status {
      display: flex;
      justify-content: space-between;
      background-color: #f0f0f0;
      padding: 10px;
      border-radius: 8px;
      margin-bottom: 10px;
    }
    .status-item {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    .status-label {
      font-size: 12px;
      color: #666;
    }
    .status-value {
      font-weight: bold;
      font-size: 16px;
    }
    button {
      background-color: #4CAF50;
      color: white;
      border: none;
      padding: 10px 20px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
      margin: 4px 2px;
      cursor: pointer;
      border-radius: 4px;
    }
    button:disabled {
      background-color: #cccccc;
      cursor: not-allowed;
    }
    .log {
      height: 200px;
      overflow-y: auto;
      background-color: #333;
      color: #f0f0f0;
      padding: 10px;
      font-family: monospace;
      border-radius: 4px;
    }
    .summary {
      background-color: #e9f7ef;
      padding: 15px;
      border-radius: 8px;
      margin-top: 20px;
      border-left: 5px solid #27ae60;
    }
  </style>
</head>
<body>
  <h1>Coze API 流式响应测试</h1>
  
  <div class="container">
    <div class="card">
      <h2>测试配置</h2>
      <p><strong>测试查询:</strong> "大理有什么好玩的地方？请详细介绍几个景点。"</p>
      <p><strong>用户ID:</strong> user_9bee6d95</p>
      <p><strong>会话ID:</strong> conv_1750059642338</p>
      <button id="startTest">开始测试</button>
    </div>
    
    <div class="card">
      <h2>响应状态</h2>
      <div class="status">
        <div class="status-item">
          <div class="status-label">状态</div>
          <div class="status-value" id="status">等待开始</div>
        </div>
        <div class="status-item">
          <div class="status-label">已接收块数</div>
          <div class="status-value" id="chunkCount">0</div>
        </div>
        <div class="status-item">
          <div class="status-label">耗时</div>
          <div class="status-value" id="elapsedTime">0ms</div>
        </div>
        <div class="status-item">
          <div class="status-label">首块延迟</div>
          <div class="status-value" id="firstChunkDelay">-</div>
        </div>
      </div>
      
      <h3>响应内容</h3>
      <div class="response" id="response"></div>
      
      <div class="summary" id="summary" style="display: none;">
        <h3>测试结果摘要</h3>
        <div id="summaryContent"></div>
      </div>
    </div>
    
    <div class="card">
      <h2>事件日志</h2>
      <div class="log" id="log"></div>
    </div>
  </div>
  
  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const startButton = document.getElementById('startTest');
      const statusEl = document.getElementById('status');
      const chunkCountEl = document.getElementById('chunkCount');
      const elapsedTimeEl = document.getElementById('elapsedTime');
      const firstChunkDelayEl = document.getElementById('firstChunkDelay');
      const responseEl = document.getElementById('response');
      const logEl = document.getElementById('log');
      const summaryEl = document.getElementById('summary');
      const summaryContentEl = document.getElementById('summaryContent');
      
      let ws;
      let startTime;
      let chunkCount = 0;
      let timer;
      let fullResponse = '';
      
      const log = (message) => {
        const timestamp = new Date().toISOString();
        const logMessage = `[${timestamp}] ${message}`;
        const logEntry = document.createElement('div');
        logEntry.textContent = logMessage;
        logEl.appendChild(logEntry);
        logEl.scrollTop = logEl.scrollHeight;
      };
      
      startButton.addEventListener('click', () => {
        startTest();
      });
      
      function startTest() {
        if (ws) {
          ws.close();
        }
        
        // 重置状态
        startTime = Date.now();
        chunkCount = 0;
        fullResponse = '';
        responseEl.textContent = '';
        statusEl.textContent = '连接中...';
        chunkCountEl.textContent = '0';
        elapsedTimeEl.textContent = '0ms';
        firstChunkDelayEl.textContent = '-';
        summaryEl.style.display = 'none';
        
        // 清除之前的计时器
        if (timer) {
          clearInterval(timer);
        }
        
        // 启动计时器
        timer = setInterval(() => {
          const elapsed = Date.now() - startTime;
          elapsedTimeEl.textContent = `${elapsed}ms`;
        }, 100);
        
        // 禁用开始按钮
        startButton.disabled = true;
        
        // 连接WebSocket
        ws = new WebSocket('ws://localhost:9527');
        
        ws.onopen = () => {
          log('WebSocket连接已建立');
          statusEl.textContent = '已连接';
          
          // 发送初始化消息
          ws.send(JSON.stringify({
            type: 'init'
          }));
          
          log('发送初始化消息');
          statusEl.textContent = '请求中...';
        };
        
        ws.onmessage = (event) => {
          try {
            const message = JSON.parse(event.data);
            
            switch (message.type) {
              case 'event':
                if (message.eventType === 'Message') {
                  chunkCount++;
                  chunkCountEl.textContent = chunkCount;
                  
                  if (chunkCount === 1) {
                    const firstChunkDelay = message.timestamp - startTime;
                    firstChunkDelayEl.textContent = `${firstChunkDelay}ms`;
                    statusEl.textContent = '接收中...';
                  }
                  
                  try {
                    const data = JSON.parse(message.data);
                    
                    if (data.content) {
                      try {
                        const parsedContent = JSON.parse(data.content);
                        
                        if (parsedContent.output && typeof parsedContent.output === 'string') {
                          try {
                            const outputData = JSON.parse(parsedContent.output);
                            
                            if (outputData.content) {
                              // 追加内容
                              fullResponse += outputData.content;
                              responseEl.textContent = fullResponse;
                            }
                          } catch (e) {
                            // 解析失败，直接显示output
                            fullResponse += parsedContent.output;
                            responseEl.textContent = fullResponse;
                          }
                        }
                      } catch (e) {
                        // 解析失败，直接显示content
                        fullResponse += data.content;
                        responseEl.textContent = fullResponse;
                      }
                    }
                  } catch (e) {
                    log(`解析消息数据失败: ${e.message}`);
                  }
                }
                
                log(`收到${message.eventType}事件 (块 #${message.chunkNumber})`);
                break;
                
              case 'summary':
                // 显示测试结果摘要
                summaryEl.style.display = 'block';
                summaryContentEl.innerHTML = `
                  <p><strong>总请求时间:</strong> ${message.totalTime}ms</p>
                  <p><strong>首个数据块延迟:</strong> ${message.timeToFirstChunk}ms</p>
                  <p><strong>总数据块数量:</strong> ${message.totalChunks}</p>
                  <p><strong>平均每块接收间隔:</strong> ${message.avgChunkInterval.toFixed(2)}ms</p>
                `;
                break;
                
              case 'end':
                statusEl.textContent = '已完成';
                startButton.disabled = false;
                
                // 清除计时器
                if (timer) {
                  clearInterval(timer);
                }
                
                log(`请求完成: ${message.message}`);
                break;
                
              case 'error':
                statusEl.textContent = '错误';
                startButton.disabled = false;
                
                // 清除计时器
                if (timer) {
                  clearInterval(timer);
                }
                
                log(`错误: ${message.message}`);
                break;
            }
          } catch (error) {
            log(`解析WebSocket消息失败: ${error.message}`);
          }
        };
        
        ws.onclose = () => {
          log('WebSocket连接已关闭');
          startButton.disabled = false;
          
          // 如果状态还是"连接中"或"请求中"，则显示错误
          if (statusEl.textContent === '连接中...' || statusEl.textContent === '请求中...') {
            statusEl.textContent = '连接失败';
          }
          
          // 清除计时器
          if (timer) {
            clearInterval(timer);
          }
        };
        
        ws.onerror = (error) => {
          log(`WebSocket错误: ${error.message || '未知错误'}`);
          statusEl.textContent = '错误';
          startButton.disabled = false;
          
          // 清除计时器
          if (timer) {
            clearInterval(timer);
          }
        };
      }
    });
  </script>
</body>
</html>
  